package com.qiangesoft.blog.system.aspect;

import cn.dev33.satoken.stp.StpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.qiangesoft.blog.common.annotation.OperationLog;
import com.qiangesoft.blog.common.constants.Constants;
import com.qiangesoft.blog.common.utils.AspectUtils;
import com.qiangesoft.blog.common.utils.DateUtils;
import com.qiangesoft.blog.common.utils.IpUtils;
import com.qiangesoft.blog.system.entity.SysExceptionLog;
import com.qiangesoft.blog.system.entity.SysOperationLog;
import com.qiangesoft.blog.system.mapper.SysExceptionLogMapper;
import com.qiangesoft.blog.system.mapper.SysOperationLogMapper;
import com.qiangesoft.blog.system.pojo.vo.SysUserDetailVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.HashMap;

/**
 * 操作日志切面处理类
 *
 * @author qiange
 */
@Aspect
@Component
@Slf4j
@RequiredArgsConstructor
public class OperationLogAspect {

    private final SysOperationLogMapper operationLogMapper;

    private final SysExceptionLogMapper exceptionLogMapper;

    /**
     * 开始时间
     */
    Date startTime;

    @Pointcut(value = "@annotation(operationLog)")
    public void pointcut(OperationLog operationLog) {

    }

    @Around(value = "pointcut(operationLog)")
    public Object doAround(ProceedingJoinPoint joinPoint, OperationLog operationLog) throws Throwable {
        startTime = DateUtils.getNowDate();
        //先执行业务
        Object result = joinPoint.proceed();
        try {
            // 日志收集
            handle(joinPoint, getHttpServletRequest());
        } catch (Exception e) {
            log.error("日志记录出错!", e);
        }
        return result;
    }

    @AfterThrowing(value = "pointcut(operationLog)", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, OperationLog operationLog, Throwable e) throws Exception {
        HttpServletRequest request = getHttpServletRequest();
        String type = request.getMethod();
        String url = request.getRequestURI();
        String ip = IpUtils.getIp(request);
        String operationName = AspectUtils.INSTANCE.parseParams(joinPoint.getArgs(), operationLog.value());
        // 获取参数名称字符串
        String paramsJson = getParamsJson((ProceedingJoinPoint) joinPoint);
        SysUserDetailVO user = (SysUserDetailVO) StpUtil.getSession().get(Constants.CURRENT_USER);

        SysExceptionLog exception = SysExceptionLog.builder()
                .requestUrl(url)
                .type(type)
                .ip(ip)
                .ipSource(IpUtils.getIp2region(ip))
                .params(paramsJson)
                .username(user.getUsername())
                .method(joinPoint.getSignature().getName())
                .exceptionJson(JSON.toJSONString(e)).exceptionMessage(e.getMessage())
                .operation(operationName)
                .createTime(LocalDateTime.now())
                .build();
        exceptionLogMapper.insert(exception);
    }

    /**
     * 管理员日志收集
     *
     * @param point
     * @throws Exception
     */
    private void handle(ProceedingJoinPoint point, HttpServletRequest request) throws Exception {

        Method currentMethod = AspectUtils.INSTANCE.getMethod(point);

        //获取操作名称
        OperationLog annotation = currentMethod.getAnnotation(OperationLog.class);

        boolean save = annotation.save();

        String operationName = AspectUtils.INSTANCE.parseParams(point.getArgs(), annotation.value());
        if (!save) {
            return;
        }
        // 获取参数名称字符串
        String paramsJson = getParamsJson(point);

        // 当前操作用户
        SysUserDetailVO user = (SysUserDetailVO) StpUtil.getSession().get(Constants.CURRENT_USER);
        String type = request.getMethod();
        String ip = IpUtils.getIp(request);
        String url = request.getRequestURI();

        // 存储日志
        Date endTime = new Date();
        Long spendTime = endTime.getTime() - startTime.getTime();
        SysOperationLog operationLog = new SysOperationLog();
        operationLog.setUsername(user.getNickname());
        operationLog.setRequestUrl(url);
        operationLog.setType(type);
        operationLog.setOperationName(operationName);
        operationLog.setIp(ip);
        operationLog.setSource(IpUtils.getIp2region(ip));
        operationLog.setSpendTime(spendTime);
        operationLog.setParamsJson(paramsJson);
        operationLog.setClassPath(point.getTarget().getClass().getName());
        operationLog.setMethodName(point.getSignature().getName());
        operationLogMapper.insert(operationLog);
    }

    /**
     * 获取参数
     *
     * @param joinPoint
     * @return
     */
    private String getParamsJson(ProceedingJoinPoint joinPoint) {
        // 参数值
        Object[] args = joinPoint.getArgs();
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        String[] parameterNames = methodSignature.getParameterNames();

        // 通过map封装参数和参数值
        HashMap<String, Object> paramMap = new HashMap<>();
        for (int i = 0; i < parameterNames.length; i++) {
            paramMap.put(parameterNames[i], args[i]);
        }

        boolean isContains = paramMap.containsKey("request");
        if (isContains) paramMap.remove("request");
        return JSONObject.toJSONString(paramMap);
    }

    /**
     * 获取请求对象
     *
     * @return
     */
    private HttpServletRequest getHttpServletRequest() {
        // 获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 从获取RequestAttributes中获取HttpServletRequest的信息
        assert requestAttributes != null;
        return (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
    }
}
